package WIAB::Model::Content;

use strict;
use warnings;
use base 'Catalyst::Model';

use Path::Class;
use File::Slurp;
use File::Basename;

use Text::Textile;
my $textile = Text::Textile->new();
$textile->css(0);

use Lingua::EN::Summarize; # provides summarize(text, maxlength => $num, filter => 'html')

use HTTP::Date; # provides time2str($epoch_str)

=head1 get_xhtml_content

Takes a L<Class::Path::Dir> or L<Path::Class::File>->new('index.pod')
and obtains the contents of the file index.pod as xhtml.  Processes
the content of the file into XHTML with L<Text::Textile>.

=cut

sub get_xhtml_content {
    my ($self, $path) = @_;
    my ($content, $error, $title, $html);
    my $file;
    # add this conditional as the RSS code uses files not dir paths
    # for accessors.

    if ($path->isa("Path::Class::Dir") ) {
        $file = $path->file('index.pod')->absolute;
    }
    else {
        $file = $path->absolute;
    }
    eval {
        my $content = read_file("$file"); # interpolated string required
        $title = _get_title($content);
        $html = $textile->process($content);
    };
    if ($@) {
        $error = $@;
        $title = 'Error';
    }
    return ($title, $html, $error);
}

=head1 _get_title ($content)

Gets a title for the HTML file from the content.  Takes up to the first blank like of Textile input and returns a maximum length string of 64 chars.

=cut

sub _get_title {
    my $content = shift;
    my ($sum) = $content =~ /.*?\s+(.*?)\n\n/s;
    if ($sum and length ($sum)  > 64) {
        $sum = summarize($sum, maxlength => 64);
    }
    $sum = '' if !$sum;
    return $sum;
}


=head1 make_breadcrumbs

Makes a breadcrumb trail which is an array with a single key/value
hash as each array item.  key/value pair represent "link text" => link
path.

=cut

sub make_breadcrumbs {
    my ($self, $path) = @_;
    my @breadcrumbs;
    # traverse down path to current level, adding to list for link construction.
    my $url_path = '';
    my $base = Path::Class::Dir->new(WIAB->config->{content});
    for my $i ( 0 .. $#{$path}-1 ) {
        if ($i == 0 ) {
            $url_path = $path->[$i];
        }
        else {
            my @p = @$path;
            $url_path = join "/", @p[0..$i];
        }
        my $linktxt = _replace_spaces($path->[$i]);
        my @p = @$path;
        my @pathpart = @p[0..$i];
        if (! $self->_is_terminal(\@pathpart, $base)) {
            $linktxt = "» " . $linktxt;
        }
        push @breadcrumbs, ( { $linktxt => $url_path } );
    }

    # then construct a list of links at the current level.
    my $dir = $base->subdir(@$path);
    $dir = $dir->parent unless ! scalar (@$path) ; # home page is special case
    my @baselevel = _get_cwd_links_in_order($dir);
    for my $i  (0 .. $#baselevel) {
        my $url_endpath = $url_path;
        my @istop;
        @istop = split /\//, $url_endpath if $url_endpath;
        if (scalar @istop ) {
            $url_endpath = $url_endpath . '/';
        }
        $url_endpath = $url_endpath . $baselevel[$i];
        my $linktxt = _replace_spaces ($baselevel[$i]);
        my @pathpart;
        if ($url_path) {
            @pathpart = (split ('/', $url_path), $baselevel[$i]) if $url_path;
        }
        else {
            @pathpart = $baselevel[$i];
        }
        if (! $self->_is_terminal(\@pathpart, $base)) {
            $linktxt = "» " . $linktxt;
        }
        push @breadcrumbs, ( { $linktxt => $url_endpath } );
    }
    return \@breadcrumbs;
}

=head1 make_menu

makes a sidebar menu of pages at current level.  Key value pairing as
per L<#make_breadcrumbs>.

=cut

sub make_menu {
    my ($self, $path ) = @_;
    my $dir = dir(WIAB->config->{'content'})->subdir(@$path);
    my @menu;
    my $DH;
    opendir $DH, "$dir";
    my @baselevel = grep {-d "$dir/$_" && /^\w+/ } readdir $DH;
    closedir SDH;
    # there are sibling directories
    if (@baselevel) {
        foreach (@baselevel) {
            my $url_path = join "/", (@$path, $_);
            my $linktxt = _replace_spaces ($_);
            if ( ! $self->_is_terminal([$_], $dir)) {
                $linktxt = "» ".$linktxt;
            }
            push @menu, ( { $linktxt => $url_path } );
        }
    }
    # we're a terminal node
    else {
        pop @$path;
        if (@$path) {
            my $url_path = '';
            if (scalar @$path) {
                $url_path = join "/", (@$path, $_) if $_;
            }
            my $linktxt = _replace_spaces ($_);
            push @menu , { $linktxt => $url_path } if $linktxt;
        }
        if (!@menu) {
            push @menu, { "Back to Home" => undef };
        }
    }
    return \@menu;
}

=head1 _get_cwd_links_in_order($dir);

get the list of links in the current dir, preferably from the file
_order otherwise from fiddling around with the contents of _order and
the readdir read of file contents.

=cut

sub _get_cwd_links_in_order {
    my $cwd = shift; #dir isa Path::Class::Dir

    # get ordered list of links if possible.
    my @ordered = ();
    if ( -e $cwd->file('_order') ) {
        @ordered = read_file($cwd->file('_order'));
    }
    my $DH;
    opendir $DH, "$cwd";
    my @dirlist = grep {-d "$cwd/$_" && /^\w+/ } readdir $DH;
    closedir SDH;

    my %dirlist;
    map { $dirlist{$_} = undef } @dirlist;
    # compare @ordered with @dirlist.
    
    
    
    # push anything that isn't in @ordered but is in @dirlist onto end
    # of @ordered

    # write @ordered if nescessary

    # stub to maintain functional code during dev of this feature:
    my @links = @dirlist;
    
    return @links
}


=head1 _is_terminal

Utility function - given a Class::Path::Dir path returns 1 if it is a
leaf (ie terminal) node, and 0 otherwise.

=cut

sub _is_terminal {
    my ($self, $path, $root) = @_;
    my $end = $root->subdir(@$path);
    my $DH;
    opendir $DH, "$end";
    my @baselevel = grep {-d "$end/$_" && /^\w+/ } readdir $DH;
    closedir SDH;
    if (scalar(@baselevel) == 0) { return 1} else {return 0}
}

=head1 _replace_spaces

sub as in WIAB::Model::Edit.  Needs to be factored into utility class.

=cut

sub _replace_spaces {
    my $txt = shift;
    if ($txt) {
        $txt =~ s/_+/ /g;
        $txt =~ s/\/$//;
    }
    return $txt;
}


=head1 get_last_modified_time($path_args, $start_dir, $c->user)

Returns the most recently modified file time of the directories in the
cwd ($start_dir->dir(@$path_args) sub directories, and each of the
parents of the $start_dir->dir(@$path_args)->parents (until
"$orig_start_dir" eq "$start_dir->parent" ).

Because of the dynamic creation of menus the last modified time of the
currently accessed file is not nescessarily when the menu was last
accessed, hence the need for some more complex logic here.

Note that although we check whether we need a last modified time by
passing in $c->user this is only used to make the code slightly more
efficient.  The page headers are sorted out in
WIAB::Controller::Root->end.

=cut

sub get_last_modified_time {
    my ($self, $path, $start_dir, $user) = @_;
    my $file;
    my $top_dir = scalar($start_dir);
    my @mtimes;
    my $max_date;
    if ($user) {
        $max_date = time();
    }
    else {
        # get last_modified for home page
        push @mtimes, (stat "$start_dir->file('index.pod')")[9];

        # get last_modified for page being rendered
        $start_dir = $start_dir->subdir(@$path);
        $file = 
            push @mtimes, (stat $start_dir->file('index.pod'))[9];

        # then for dependent pages on the current level
        my $DH;
        opendir $DH, "$start_dir";
        while (my $dir = readdir($DH) ) {
            my $file = dir($dir)->file('index.pod');
            push @mtimes, (stat "$file")[9] if -d $dir;
        }
        closedir SDH;

        # then for dependent pages up the dir tree
        while ("$start_dir" ne "$top_dir") {
            $start_dir = $start_dir->parent;
            push @mtimes, (stat $start_dir->file('index.pod'))[9];
        }

        # find newest modified file and return
        $max_date = max (@mtimes);
    }
    use YAML;
    return time2str($max_date);
}

=head1 max (@$int)

the classic return the maximum of a list of numbers sub :)

=cut

sub max {
    my $max = shift;
    foreach my $i (@_) {
        if ($i > $max) {$max = $i;}
    }
    return $max;
}

=head1 get_rss_file_list($root_dir, $path_requested)

get the list of files at or below the current path newest first.

=cut

sub get_rss_file_list {
    my ( $self, $root, $path ) = @_;
    if ($path) {
        $root = $root->subdir(@$path);
    }

    # run through the files and sort by mtime
    my @files = map { $_->[1] }
        sort { $a->[0] <=> $b->[0] }
            map  { [ -M $_, $_ ] }
                grep { /\w/ }
                    File::Finder->type('f')->in("$root");

    @files = grep {!/\.svn/ && 'index.pod'} @files;
    map {$_ = Path::Class::File->new($_)} @files;
    return \@files;
}

=head1 get_rss_data

Content accessors only used for RSS.  Returns a list of html entries
and metadata in an array of hashes used by the rss action.

=cut

sub get_rss_data {
    my ($self, $root, $path) = @_;
    my $files = $self->get_rss_file_list($root, $path);
    my @entries; # each entry for the rss feed
    foreach my $file (@$files) {
        # build up the rss feed entry by entry
        my %entry;
        # we call uri_for on $entry{link} this when it's back in the
        # controller to avoid having to pass $c into the model code
        ($entry{link}) = "$file" =~ /$root\/(.*?)index\.pod/;
        $entry{title} = _replace_spaces($entry{link});
        $entry{title} = 'Home' if $entry{title} eq '';
        (undef, $entry{summary}, undef) = $self->get_xhtml_content($file);
        $entry{issued} = (stat $file)[9];
        push @entries, \%entry
    }
    return \@entries;
}

=head1 NAME

WIAB::Model::Content - Catalyst Model

=head1 SYNOPSIS

See L<WIAB>

=head1 DESCRIPTION

Catalyst Model.

=head1 AUTHOR

Kieren Diment

=head1 LICENSE

This library is free software, you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

1;
